Amazon Elasticsearch Serviceの手動スナップショットを管理するLambda関数を作成してみた
Amazon Elasticsearch Serviceの手動スナップショットとは
こんにちは、のんピ です。
皆さんはAmazon Elasticsearch Service(以降Amazon ES)のスナップショットに2つ種類があることをご存知ですか? 私は知りませんでした。
まず、Elasticsearchのスナップショットは、ElasticsearchクラスターのインデックスとStateのバックアップです。Stateとは、クラスタの設定、ノード情報、インデックスの設定、シャードの割り当てなどのことです。
そして、Amazon ESには、自動スナップショットと手動スナップショットと2種類のスナップショットがあります。2つのスナップショットの違いとして、以下が挙げられます。
- 自動スナップショット
- クラスターのリカバリー用途で使われる
- クラスターのステータスが赤くなったり、データが失われたりした場合に、ドメインを復元するために使用する
- 事前設定された Amazon S3 バケットに追加料金なしで保存される
- 手動スナップショット
- クラスターのリカバリーや、あるクラスターから別のクラスターへのデータ移動のために使用される
- スナップショットはAmazon S3バケットに保存され、標準のS3料金が適用される
- 手動スナップショットがあれば、そのスナップショットを使ってAmazon ESドメインに移行することができる(詳細: Amazon Elasticsearch Service への移行)
また、AWS公式ドキュメントに記載がある通り、自動スナップショットの作成間隔と保持期間は以下の通りです。
- Elasticsearch 5.3以降のドメイン
- 1時間ごとに自動でスナップショットを作成し、最大 336 個のスナップショットを 14 日間保持
- Elasticsearch 5.1以前のドメイン
- 指定した時刻に毎日自動的にスナップショットを作成し、14 個のスナップショットを保持する。30 日以上はスナップショットデータを保持しない
そのため、Amazon ESを運用する上で、以下のような場面に遭遇することが考えられる場合は、手動スナップショットを作成する必要があります。
- 既存のAmazon ESドメインから、新しいAmazon ESドメインにデータをリストアしたいとき
- 14日以上前の状態にリストアしたいとき
例えば、Amazon ESドメインで障害が発生し、復旧できないまま14日以上経過する場合、正常なスナップショットが全て削除されてしまい、リストアができない(=構築し直し)といったことも起こり得ます。
そこで、今回は手動スナップショットをLambda関数を使って楽に管理する仕組みを実装したいと思います。
いきなりまとめ
- 手動スナップショットを作成するためには、スナップショットのリポジトリを登録する必要がある
- 手動スナップショット作成などのリクエストを投げるために、Amazon ESドメインで操作元のIAMユーザーもしくは、IAMロールを指定する必要がある
- Amazon ESのスナップショットに対して
_search
で詳細な検索をすることはできない - Elasticsearchの運用支援ツールであるCuratorを使用して、指定した期間よりも以前に作成された手動スナップショットを削除する
- Elasticsearchのクライアントと、ドメインのバージョンが一致しない場合は、エラーが出力されて正常に動作しない
- スナップショットの削除は1つあたり15-30秒かかるため、Lambda関数で複数のスナップショットをまとめて削除をする場合は、タイムアウト値を長くする
検証の構成
一からAmazon ESを構築してログを可視化するのも手間がかかるため、SIEM on Amazon ESを使用します。
検証する内容としては、以下の4つです。
- CloudTrailのログをSIEM on Amazon ESで取り込み、可視化する
- Amazon ESのスナップショットを定期的(30分ごと)に作成する
- 古いAmazon ESのスナップショット(2日前よりも前)を定期的(1日ごと)に削除する
- 作成したスナップショットからリストアする
全体の構成は以下の通りです。
作成するLambda関数は以下の3つです。
- 手動スナップショット用リポジトリの登録
- 手動スナップショットの作成
- 指定した期間よりも以前に作成された手動スナップショットの削除
1. 手動スナップショット用リポジトリの登録
について補足します。
Open Distro for Elasticsearchの公式ドキュメントを確認すると、以下のような記載があります。
Register repository
Before you can take a snapshot, you have to “register” a snapshot repository. A snapshot repository is just a storage location: a shared file system, Amazon S3, Hadoop Distributed File System (HDFS), Azure Storage, etc.
「スナップショットを作成する前に、スナップショットリポジトリの登録が必要」と記載されています。そのため、手動スナップショット用のリポジトリの登録のLambda関数も作成します。
SIEM on Amazon ESのデプロイとCloudTrailの設定
まず、SIEM on Amazon ESをデプロイします。
SIEM on Amazon ESのデプロイは以下の記事と同様にCloudFormationを使って行いました。
しばらく経った後、CloudFormationのコンソールを確認すると、SIEM on Amazon ESのスタックであるaes-siem
のステータスがCREATE_COMPLETE
になっていることが確認できます。
SIEM on Amazon ESデプロイ後の以下の作業については、上述した記事の通り行います。
- Kibanaへのログイン
- Dashboard等設定のインポート
- CloudTrailのログのSIEM on Amazon ESのログ収集用S3バケットへの出力設定
最終的には以下のようにCloudTrailのログが可視化できればOKです。
IAMの設定
スナップショット管理用IAMロールの確認
SIEM on Amazon ESのデプロイ後、IAMのコンソールからaes-siem-snapshot-role
があることを確認します。
このIAMロールを使って、Amazon ESは手動スナップショットの作成、リスト、削除を行います。
aes-siem-snapshot-role
のIAMポリシーは以下のように設定されてます。SIEM on Amazon ESではなく、自分でAmazon ESを構築される方はご参考ください。
{ "Version": "2012-10-17", "Statement": [{ "Action": [ "s3:ListBucket" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::<Amazon ESスナップショット保存用S3バケットの名前>" ] }, { "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::<Amazon ESスナップショット保存用S3バケットの名前>/*" ] } ] }
また、Amazon ESがこのaes-siem-snapshot-role
を受け取り、S3の操作をするために、信頼されたエンティティとして、以下のようにes.amazonaws.com
を設定します。(SIEM on Amazon ESの場合は既に設定されています)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "es.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
Lambda関数用のIAMポリシーとIAMロールの作成
Lambda関数からElasticsearchのクライアントを使ってAmazon ESにクエリを投げたり、Amazon ESドメインにaes-siem-snapshot-role
を渡すために、IAMポリシーとIAMロールを作成します。
まず、IAMポリシーです。
cm-aes-siem-snapshot-policy
というIAMポリシーを作成しました。ポリシーの内容は以下の通りです。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::<AWSアカウントID>:role/aes-siem-snapshot-role" }, { "Effect": "Allow", "Action": [ "es:ESHttpGet", "es:ESHttpPost", "es:ESHttpPut", "es:ESHttpDelete" ], "Resource": "arn:aws:es:us-east-1:<AWSアカウントID>:domain/aes-siem/*" } ] }
続いて、IAMロールの作成です。
Lambda関数にアタッチするIAMロールとしてcm-aes-siem-snapshot-role
というIAMロールを作成しました。IAMロールには以下のIAMポリシーをアタッチしています。
cm-aes-siem-snapshot-policy
AWSLambdaBasicExecutionRole
(CloudWatch Logsへのログ出力をするためにアタッチ)
また、Lambda関数がこのcm-aes-siem-snapshot-role
を受け取り、Amazon ESの操作をするために、信頼されたエンティティとして以下のようにlambda.amazonaws.com
を設定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
Amazon ESのロール設定
Amazon ESが手動スナップショットを管理するために使用するmanage_snapshots
ロールとcm-aes-siem-snapshot-role
のマッピングと、不足している権限を追加します。
まず、manage_snapshots
ロールとcm-aes-siem-snapshot-role
のマッピングです。
KibanaにログインしてSecurity
- Roles
からmanage_snapshots
をクリックし、Mapped users
タブのManage mapping
をクリックします。
Backend roles
に作成したcm-aes-siem-snapshot-role
のARNを入力して、Map
をクリックします。
続いて、不足している権限の追加です。
後述するElasticsearchの運用支援ツールであるCuratorを使ってスナップショットを削除する際に、cluster:monitor/tasks/lists
への許可が必要になります。
manage_snapshots
のPermissions
タブのEdit role
をクリックします。
Cluster permissions
のテキストエリアでcluster_monitor
を入力し、Update
をクリックします。
最終的に、ロールの一覧でmanage_snapshots
を表示したときに、以下のようになっていればOKです。
手動スナップショット用リポジトリの登録
Lambda関数を作成するための準備
作成するLambda関数にはrequests_aws4auth
などパッケージをインポートする必要があります。
そのため、必要なパッケージをzipでまとめたデプロイパッケージを作成する必要があります。
デプロイパッケージ作成のため、以下の作業を実施しました。
# Pythonのバージョン確認 > python3 --version Python 3.9.3 # 2021/8/8現在、LambdaがサポートしているPythonは3.8まで # そのため、pyenvで、Python3.8系をインストールする > pyenv local 3.8.9 > python3 --version Python 3.8.9 # 手動スナップショット用リポジトリの登録をするLambda関数の作業用ディレクトリの作成 > mkdir register-aes-manual-snapshot-repository > cd register-aes-manual-snapshot-repository # venvで仮想環境を作成 > python -m venv venv # 仮想環境をアクティベート # 私の環境ではfishを使用しているので、. ./venv/bin/activate.fish を実行 # 以降の操作は全て仮想環境上で行う > . ./venv/bin/acrivate.fish # pipのアップグレード > pip install --upgrade pip # 仮想環境に必要なパッケージをインストール > pip install boto3 requests requests_aws4auth # インストール済みパッケージ一覧を確認 > pip list Package Version ------------------ --------- boto3 1.18.15 botocore 1.21.15 certifi 2021.5.30 charset-normalizer 2.0.4 idna 3.2 jmespath 0.10.0 pip 21.2.2 python-dateutil 2.8.2 requests 2.26.0 requests-aws4auth 1.1.1 s3transfer 0.5.0 setuptools 49.2.1 six 1.16.0 urllib3 1.26.6 # デプロイパッケージ用のディレクトリの作成 > mkdir dist > mkdir package # デプロイパッケージ作成用のシェルスクリプトの作成 > vi build.sh > cat build.sh #!/bin/bash rm -rf package/* cp *.py package/ pip install -r requirements.txt -t ./package/ cd ./package/ zip -r ../dist/lambda.zip . # デプロイパッケージ作成用のシェルスクリプトに実行権限を追加 > chmod +x build.sh
コードの説明
こちらのAWS公式ドキュメントに記載されているサンプルのコードを参考に作成しました。
S3バケットをスナップショットのリポジトリとして登録する際には、以下いくつかの設定項目があります。
項目 | 説明 |
---|---|
base_path | スナップショットを保存するS3バケット内のパス (例:my/snapshot/directory) 指定しない場合、スナップショットはS3バケットのルートに保存される (オプション) |
bucket | S3バケットの名前 (必須) |
buffer_size | chunk_sizeで指定したチャンクをbuffer_sizeで指定した単位に分割し、別のAPIを使用してS3に送信する際の閾値 デフォルトは、100MBまたはJavaヒープの5%の2つの値のうち小さい方 有効な値は、5mbから5gbの間 (オプション変更は非推奨) |
canned_acl | repository-s3プラグインがS3にオブジェクトを作成する際に、オブジェクトにACLを追加することができる デフォルトはprivate (オプション) |
chunk_size | スナップショット操作時にファイルをチャンクに分割する (例:64mb、1gb) デフォルトは1gb (オプション) |
client | クライアントの設定(s3.client.default.access_keyなど)を指定する際に、デフォルト以外の文字列(s3.client.backup-role.access_keyなど)を使用することができる 別の名前を使用した場合は、この値を一致するように変更するように指定する 既定値および推奨値はdefault (オプション) |
compress | メタデータファイルを圧縮するかどうかを指定する この設定は、データファイルには影響しない データファイルはインデックスの設定によってはすでに圧縮されている場合がある デフォルトはfalse (オプション) |
max_restore_bytes_per_sec | スナップショットを復元する際の最大速度 デフォルトは40MB/秒(40m) (オプション) |
max_snapshot_bytes_per_sec | スナップショットを取得する際の最大レート 既定値は40MB/秒(40m) (オプション) |
readonly | リポジトリを読み取り専用にするかどうか あるクラスター(登録時に "readonly":false)から別のクラスター(登録時に "readonly":true)に移行する際に利用する (オプション) |
server_side_encryption | S3バケット内のスナップショットファイルを暗号化するかどうかを指定する この設定では、S3で管理された鍵でAES-256を使用します デフォルトはfalse (オプション) |
storage_class | スナップショットファイルを保存するS3のストレージクラスを指定する デフォルトはstandard glacierおよびdeep_archiveの使用は非推奨 (オプションで指定) |
(参考: Elasticsearch - Take and Restore Snapshots - Amazon S3)
また、Amazon ESのエンドポイントやIAMロールを変更する度にデプロイパッケージを作成し直すのが手間なので、環境変数で各種パラメーターを指定するようにしています。
実際のコードは以下の通りです。
import os import boto3 import requests from logging import getLogger, StreamHandler, DEBUG from requests_aws4auth import AWS4Auth logger = getLogger("urllib3") handler = StreamHandler() handler.setLevel(DEBUG) logger.setLevel(DEBUG) logger.addHandler(handler) logger.propagate = False # host is include https:// and trailing / host = os.environ["AES_HOST"] region = os.environ["REGION"] repository_path = os.environ["SNAPSHOT_REPOSITORY_PATH"] bucket_name = os.environ["BUCKET_NAME"] role_arn = os.environ["ROLE_ARN"] service = "es" # the Elasticsearch API endpoint path = repository_path url = host + path payload = { "type": "s3", "settings": { "bucket": bucket_name, "base_path": repository_path, "region": region, "role_arn": role_arn, }, } headers = {"Content-Type": "application/json"} credentials = boto3.Session().get_credentials() awsauth = AWS4Auth( credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token, ) # Register repository def lambda_handler(event, context): r = requests.put(url, auth=awsauth, json=payload, headers=headers) logger.info(r.text) return {"statusCode": r.status_code, "body": r.text}
Lambda関数の作成
それでは、Lambda関数を作成します。
まず、以下コマンドでデプロイパッケージを作成します。
# インストール済みパッケージ一覧を requirements.txt に出力 > pip freeze > requirements.txt # デプロイパッケージの作成 > ./build.sh
./build.sh
実行後のディレクトリツリーは以下のようになり、dist
配下にlambda.zip
が作成されます。
./register-aes-manual-snapshot-repository/ ├── .gitignore ├── .vscode/ │ └── settings.json ├── build.sh ├── dist/ │ └── lambda.zip ├── lambda_function.py ├── package/ ├── requirements.txt └── venv/
また、requirements.txt
は以下の通りです。black
はPythonのフォーマッターとして使用しています。
appdirs==1.4.4 black==21.7b0 boto3==1.18.15 botocore==1.21.15 certifi==2021.5.30 charset-normalizer==2.0.4 click==8.0.1 idna==3.2 jmespath==0.10.0 mypy-extensions==0.4.3 pathspec==0.9.0 python-dateutil==2.8.2 regex==2021.8.3 requests==2.26.0 requests-aws4auth==1.1.1 s3transfer==0.5.0 six==1.16.0 tomli==1.2.1 urllib3==1.26.6
続いて、Lambda関数の設定をします。
Lambdaのコンソールを開き、関数の作成
をクリックします。
関数名、ランタイム、Lambda関数に割り当てるIAMロールを指定して関数の作成
をクリックします。
次に、デプロイパッケージをアップロードします。
コード
タブからアップロード元
-.zipファイル
をクリックします。
アップロード
をクリックして、作成したデプロイパッケージを指定します。その後保存
をクリックします。
これでコードのアップロードは完了です。
次に環境変数の設定をします。
設定
タブの環境変数
から編集
をクリックします。
以下のように環境変数を設定します。なお、AES_HOST
は、Amazon ESのエンドポイントの先頭のhttps://
と末尾の/
を含めて記載します。
これで環境変数の設定も完了です。
実行してみた
それでは、Lambda関数を実行して、手動スナップショットを作成します。
テスト
タブからテスト
をクリックします。
正常に実行できれば、以下のように{"acknowledged":true}
が返ってきます。
正しくリポジトリが作成できたか確認します。
Kibanaにログインして、Dev Toolsを開きます。コンソールにGET _snapshot/
を入力して▷
をクリックします。すると、以下のように_snapshot
配下にmanual
というリポジトリが作成されたことが確認できます。
なお、cs-automated-enc
は自動スナップショット用のリポジトリです。
手動スナップショットの作成
Lambda関数を作成するための準備
手動スナップショットリポジトリ登録用のLambda関数と同様に、デプロイパッケージを作成するための作業を実施します。
# 手動スナップショットを作成するLambda関数の作業用ディレクトリの作成 > mkdir create-aes-manual-snapshot > cd create-aes-manual-snapshot # venvで仮想環境を作成 > python -m venv venv # 仮想環境をアクティベート # 私の環境ではfishを使用しているので、. ./venv/bin/activate.fish を実行 # 以降の操作は全て仮想環境上で行う > . ./venv/bin/acrivate.fish # pipのアップグレード > pip install --upgrade pip # 仮想環境に必要なパッケージをインストール > pip install boto3 requests requests_aws4auth # インストール済みパッケージ一覧を確認 > pip list Package Version ------------------ --------- boto3 1.18.15 botocore 1.21.15 certifi 2021.5.30 charset-normalizer 2.0.4 idna 3.2 jmespath 0.10.0 pip 21.2.2 python-dateutil 2.8.2 requests 2.26.0 requests-aws4auth 1.1.1 s3transfer 0.5.0 setuptools 49.2.1 six 1.16.0 urllib3 1.26.6 # デプロイパッケージ用のディレクトリの作成 > mkdir dist > mkdir package # デプロイパッケージ作成用のシェルスクリプトの作成 > vi build.sh > cat build.sh #!/bin/bash rm -rf package/* cp *.py package/ pip install -r requirements.txt -t ./package/ cd ./package/ zip -r ../dist/lambda.zip . # デプロイパッケージ作成用のシェルスクリプトに実行権限を追加 > chmod +x build.sh
コードの説明
コードは手動スナップショットリポジトリ登録用のLambda関数のコードとほぼほぼ同じです。
作成するスナップショットは、「指定したプレフィックス-YYYY-MM-DDtHH-MM-SS」という名前で作成します。
実際のコードは以下の通りです。
import os import boto3 import requests from logging import getLogger, StreamHandler, DEBUG from requests_aws4auth import AWS4Auth from datetime import datetime logger = getLogger("urllib3") handler = StreamHandler() handler.setLevel(DEBUG) logger.setLevel(DEBUG) logger.addHandler(handler) logger.propagate = False # host is include https:// and trailing / host = os.environ["AES_HOST"] region = os.environ["REGION"] repository_path = os.environ["SNAPSHOT_REPOSITORY_PATH"] snapshot_prefix = os.environ["SNAPSHOT_PREFIX"] service = "es" credentials = boto3.Session().get_credentials() awsauth = AWS4Auth( credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token, ) # Create Elasticsearch Snapshot def lambda_handler(event, context): # the Elasticsearch API endpoint path = ( repository_path + snapshot_prefix + "-" + datetime.now().strftime("%Y-%m-%dt%H-%M-%S") ) url = host + path r = requests.put(url, auth=awsauth) logger.info(r.text) return {"statusCode": r.status_code, "body": r.text}
Lambda関数の作成
手動スナップショットリポジトリ登録用のLambda関数と同様の手順でLambda関数を作成します。
以下コマンドでデプロイパッケージを作成します。
# インストール済みパッケージ一覧を requirements.txt に出力 > pip freeze > requirements.txt # デプロイパッケージの作成 > ./build.sh
./build.sh
実行後のディレクトリツリーは以下のようになり、dist
配下にlambda.zip
が作成されます。
./create-aes-manual-snapshot/ ├── .gitignore ├── .vscode/ │ └── settings.json ├── build.sh ├── dist/ │ └── lambda.zip ├── lambda_function.py ├── package/ ├── requirements.txt └── venv/
また、requirements.txt
は以下の通りです。手動スナップショットリポジトリ登録用のLambda関数と同じですね。
appdirs==1.4.4 black==21.7b0 boto3==1.18.15 botocore==1.21.15 certifi==2021.5.30 charset-normalizer==2.0.4 click==8.0.1 idna==3.2 jmespath==0.10.0 mypy-extensions==0.4.3 pathspec==0.9.0 python-dateutil==2.8.2 regex==2021.8.3 requests==2.26.0 requests-aws4auth==1.1.1 s3transfer==0.5.0 six==1.16.0 tomli==1.2.1 urllib3==1.26.6
次にLambda関数の作成です。手動スナップショットリポジトリ登録用のLambda関数と同様の手順でLambda関数を作成します。
環境変数は以下のように設定します。SNAPSHOT_PREFIX
は手動スナップショットのプレフィックスです。今回はmanual-snapshot-test
としました。
定期実行の設定
手動スナップショットを30分間隔で作成するように、EventBridgeを使って設定をします。
create-aes-manual-snapshot
のトリガーを追加
をクリックします。その後、トリガーとしてEventBridge
を選択し、ルール名や説明、スケジュールを入力して、追加
をクリックします。
実行してみた
それでは、Lambda関数を実行して、手動スナップショット用リポジトリの登録をします。
テスト
タブからテスト
をクリックします。
正常に実行できれば、以下のように{"accepted":true}
が返ってきます。
正しくスナップショットが作成できたか確認します。
Kibanaにログインして、Dev Toolsを開きます。コンソールにGET _snapshot/manual/*
を入力して▷
をクリックします。すると、以下のように_snapshot/manual
配下にスナップショットが作成されていることが確認できます。
また、手動スナップショットのリポジトリとして設定しているS3バケットを確認すると、以下のように_snapshot/manual/
ディレクトリの配下にスナップショットが作成されていることが確認できます。
もう一度、手動スナップショット作成のLambda関数を実行してみます。実行後、再度Dev Toolsと手動スナップショットのリポジトリとして設定していS3バケットを確認すると、手動スナップショットが追加されていることが確認できます。
指定した期間よりも以前に作成された手動スナップショットの削除
Lambda関数を作成するための準備
手動スナップショットリポジトリ登録用のLambda関数と同様に、デプロイパッケージを作成するための作業を実施します。
# 指定した期間よりも以前に作成された手動スナップショットの削除をするLambda関数の作業用ディレクトリの作成 > mkdir delete-aes-manual-snapshot > cd delete-aes-manual-snapshot # venvで仮想環境を作成 > python -m venv venv # 仮想環境をアクティベート # 私の環境ではfishを使用しているので、. ./venv/bin/activate.fish を実行 # 以降の操作は全て仮想環境上で行う > . ./venv/bin/acrivate.fish # pipのアップグレード > pip install --upgrade pip # 仮想環境に必要なパッケージをインストール > pip install boto3 requests requests_aws4auth elasticsearch-curator==5.7.0 elasticsearch==7.9.1 # インストール済みパッケージ一覧を確認 > pip list Package Version --------------------- --------- boto3 1.18.16 botocore 1.21.16 certifi 2021.5.30 charset-normalizer 2.0.4 click 6.7 elasticsearch 7.9.1 elasticsearch-curator 5.7.0 idna 3.2 jmespath 0.10.0 pip 21.2.3 python-dateutil 2.8.2 PyYAML 5.4.1 requests 2.26.0 requests-aws4auth 1.1.1 s3transfer 0.5.0 setuptools 49.2.1 six 1.16.0 urllib3 1.26.6 voluptuous 0.12.1 # デプロイパッケージ用のディレクトリの作成 > mkdir dist > mkdir package # デプロイパッケージ作成用のシェルスクリプトの作成 > vi build.sh > cat build.sh #!/bin/bash rm -rf package/* cp *.py package/ pip install -r requirements.txt -t ./package/ cd ./package/ zip -r ../dist/lambda.zip . # デプロイパッケージ作成用のシェルスクリプトに実行権限を追加 > chmod +x build.sh
Elasticsearch(elasticsearch
)とElasticsearchの運用支援ツールであるCurator(elasticsearch-curator
)をインストールしています。それぞれのパッケージでバージョンを指定している理由は、Amazon ESドメインとバージョンを合わせるためです。
2021/8/8時点のSIEM on Amazon ESのAmazon ESドメインのデフォルトのバージョンは7.9
です。
この2つのバージョン指定をしない場合は現時点での最新バージョンである7.14.0
がインストールされます。
> pip install boto3 requests requests_aws4auth elasticsearch-curator elasticsearch > pip list Package Version --------------------- --------- boto3 1.18.16 botocore 1.21.16 certifi 2021.5.30 charset-normalizer 2.0.4 click 7.1.2 elasticsearch 7.14.0 elasticsearch-curator 5.8.4 idna 3.2 jmespath 0.10.0 pip 21.2.3 python-dateutil 2.8.2 PyYAML 5.4.1 requests 2.26.0 requests-aws4auth 1.1.1 s3transfer 0.5.0 setuptools 49.2.1 six 1.16.0 urllib3 1.26.4 voluptuous 0.12.1
SIEM on Amazon ESドメインとクライアントのElasticsearchのバージョンが一致しない場合は、Unable to find repository "manual": Error: The client noticed that the server is not a supported distribution of Elasticsearch
と表示され、スナップショットのリポジトリであるmanual
にアクセスすることができません。
パッケージの依存関係を確認する際はpipdeptree
を使います。
実行すると、Curatorの最新バージョンである5.8.4
は、Elasticsearchの7.12.0
以上、8.0.0
未満のバージョンをインストールする必要があることがわかります。
# pipdeptree のインストール > pip install pipdeptree # パッケージの依存関係の確認 > pipdeptree elasticsearch-curator==5.8.4 - boto3 [required: >=1.17.57, installed: 1.18.16] - botocore [required: >=1.21.16,<1.22.0, installed: 1.21.16] - jmespath [required: >=0.7.1,<1.0.0, installed: 0.10.0] - python-dateutil [required: >=2.1,<3.0.0, installed: 2.8.2] - six [required: >=1.5, installed: 1.16.0] - urllib3 [required: >=1.25.4,<1.27, installed: 1.26.4] - jmespath [required: >=0.7.1,<1.0.0, installed: 0.10.0] - s3transfer [required: >=0.5.0,<0.6.0, installed: 0.5.0] - botocore [required: >=1.12.36,<2.0a.0, installed: 1.21.16] - jmespath [required: >=0.7.1,<1.0.0, installed: 0.10.0] - python-dateutil [required: >=2.1,<3.0.0, installed: 2.8.2] - six [required: >=1.5, installed: 1.16.0] - urllib3 [required: >=1.25.4,<1.27, installed: 1.26.4] - certifi [required: >=2020.12.5, installed: 2021.5.30] - click [required: >=7.0,<8.0, installed: 7.1.2] - elasticsearch [required: >=7.12.0,<8.0.0, installed: 7.14.0] - certifi [required: Any, installed: 2021.5.30] - urllib3 [required: >=1.21.1,<2, installed: 1.26.4] - pyyaml [required: ==5.4.1, installed: 5.4.1] - requests [required: >=2.25.1, installed: 2.26.0] - certifi [required: >=2017.4.17, installed: 2021.5.30] - charset-normalizer [required: ~=2.0.0, installed: 2.0.4] - idna [required: >=2.5,<4, installed: 3.2] - urllib3 [required: >=1.21.1,<1.27, installed: 1.26.4] - requests-aws4auth [required: >=1.0.1, installed: 1.1.1] - requests [required: Any, installed: 2.26.0] - certifi [required: >=2017.4.17, installed: 2021.5.30] - charset-normalizer [required: ~=2.0.0, installed: 2.0.4] - idna [required: >=2.5,<4, installed: 3.2] - urllib3 [required: >=1.21.1,<1.27, installed: 1.26.4] - six [required: Any, installed: 1.16.0] - six [required: >=1.15.0, installed: 1.16.0] - urllib3 [required: ==1.26.4, installed: 1.26.4] - voluptuous [required: >=0.12.1, installed: 0.12.1] pipdeptree==2.1.0 - pip [required: >=6.0.0, installed: 21.2.3] setuptools==49.2.1
コードの説明
こちらのAWS公式ドキュメントに記載されているサンプルコードを参考に作成しました。
スナップショットは、インデックスのGET /index-name/_search
のように、作成日時などの条件を指定して検索を行うことができません。
そのため、GET _snapshot/リポジトリ名/*
で、全てのスナップショットを取得してから、条件に合うスナップショットをフィルタリングするコードを自分で実装する必要があります。
このような場面で活躍するのが、Elasticsearchの運用支援ツールであるCuratorです。Curatorを使用することで、90日以上前や、1年以上前に作成されたスナップショットのフィルターや削除を簡単に実装することができます。
コードは上述のサンプルをベースに、手動スナップショットリポジトリ登録用のLambda関数と同じような書き方にしています。
実際のコードは以下の通りです。
import os import boto3 import curator from logging import getLogger, StreamHandler, DEBUG from requests_aws4auth import AWS4Auth from elasticsearch import Elasticsearch, RequestsHttpConnection logger = getLogger("curator") handler = StreamHandler() handler.setLevel(DEBUG) logger.setLevel(DEBUG) logger.addHandler(handler) logger.propagate = False host = os.environ["AES_HOST"] region = os.environ["REGION"] repository_name = os.environ["SNAPSHOT_REPOSITORY_NAME"] snapshot_prefix = os.environ["SNAPSHOT_PREFIX"] unit = os.environ["UNIT"] unit_count = int(os.environ["UNIT_COUNT"]) service = "es" credentials = boto3.Session().get_credentials() awsauth = AWS4Auth( credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token, ) def lambda_handler(event, context): # Build the Elasticsearch client. es = Elasticsearch( hosts=[{"host": host, "port": 443}], http_auth=awsauth, use_ssl=True, verify_certs=True, connection_class=RequestsHttpConnection, timeout=120, # Deleting snapshots can take a while, so keep the connection open for long enough to get a response. ) try: # Get all snapshots in the repository snapshot_list = curator.SnapshotList(es, repository=repository_name) # Filter by prefix snapshot_list.filter_by_regex(kind="prefix", value=snapshot_prefix) # Filter by creation_date snapshot_list.filter_by_age( source="creation_date", direction="older", unit=unit, unit_count=unit_count, ) # Delete the old snapshots. curator.DeleteSnapshots( snapshot_list, retry_interval=30, retry_count=3 ).do_action() except ( curator.exceptions.SnapshotInProgress, curator.exceptions.NoSnapshots, curator.exceptions.FailedExecution, ) as e: print(e)
curator.SnapshotList()
で現在のスナップショットの一覧を取得します。その後、snapshot_list.filter_by_regex()
とsnapshot_list.filter_by_age()
で削除対象のスナップショットのフィルターを行います。
まず、snapshot_list.filter_by_regex()
で、スナップショット名のプレフィックスを環境変数で指定した文字列でフィルターします。
次に、snapshot_list.filter_by_age()
で、スナップショットの作成日を確認し、指定した期間よりも以前に作成されたスナップショットをフィルターします。
指定した期間は、Curatorの公式ドキュメントにある通り、unit
とunit_count
を使って指定します。
unit
は時間や週、年など期間の単位を指定します。unit
に指定可能な値は以下の通りです。
- seconds
- minutes
- hours
- days
- weeks
- months
- years
unit_count
はunit
で指定した期間の数を示します。例えば{unit:weeks, unit_count:2}
の場合は2週間ということになります。
スナップショットの削除処理はcurator.DeleteSnapshots().do_action()
で行っています。
curator.DeleteSnapshots(). do_dry_run()
とすることで、実際の削除は行わずに削除対象を確認することができます。
DeleteSnapshots()
のその他のプロパティ、メソッドは、Curatorの公式ドキュメントをご確認ください。
Lambda関数の作成
手動スナップショットリポジトリ登録用のLambda関数と同様の手順でLambda関数を作成します。
以下コマンドでデプロイパッケージを作成します。
# インストール済みパッケージ一覧を requirements.txt に出力 > pip freeze > requirements.txt # デプロイパッケージの作成 > ./build.sh
./build.sh
実行後のディレクトリツリーは以下のようになり、dist
配下にlambda.zip
が作成されます。
./delete-aes-manual-snapshot/ ├── .gitignore ├── .vscode/ │ └── settings.json ├── build.sh ├── dist/ │ └── lambda.zip ├── lambda_function.py ├── package/ ├── requirements.txt └── venv/
また、requirements.txt
は以下の通りです。
appdirs==1.4.4 attrs==21.2.0 black==20.8b0 boto3==1.18.15 botocore==1.21.15 certifi==2021.5.30 charset-normalizer==2.0.4 click==6.7 elasticsearch==7.9.1 elasticsearch-curator==5.7.0 idna==3.2 jmespath==0.10.0 mypy-extensions==0.4.3 pathspec==0.9.0 python-dateutil==2.8.2 PyYAML==5.4.1 regex==2021.8.3 requests==2.26.0 requests-aws4auth==1.1.1 s3transfer==0.5.0 six==1.16.0 toml==0.10.2 tomli==1.2.1 typed-ast==1.4.3 typing-extensions==3.10.0.0 urllib3==1.26.6 voluptuous==0.12.1
次にLambda関数の作成です。手動スナップショットリポジトリ登録用のLambda関数と同様の手順でLambda関数を作成します。
環境変数はmanual
リポジトリにある、プレフィックスがmanual-snapshot-test
の2日以上前に作成されたスナップショットを削除するため、以下のように設定します。なお、AES_HOST
はAmazon ESのエンドポイントの先頭のhttps://
と末尾の/
を含めずに記載します。
また、スナップショットの削除にはしばらく時間がかかります。そのため、タイムアウトをデフォルトの3秒から15分に変更します。実際に運用する際は、削除対象のスナップショットが多すぎて、Lambdaのタイムアウト値の上限である15分を超えないように注意する必要があります。
定期実行の設定
スナップショットの削除を日次で実行するように、EventBridgeを使って設定をします。
delete-aes-manual-snapshot
のトリガーを追加
をクリックします。その後、トリガーとしてEventBridge
を選択し、ルール名や説明、スケジュールを入力して、追加
をクリックします。
実行してみた
それでは、Lambda関数を実行して、手動スナップショットを作成します。
テスト
タブからテスト
をクリックします。実行後、delete-aes-manual-snapshot
のCloudWatch Logsを確認すると、以下のように36個のスナップショットが削除されていることが確認できます。
実行時間は約11分でした。スナップショットを1つ削除するにあたり15〜30秒ほどかかるため、Lambda関数を使って大量のスナップショットを削除する際は、タイムアウトに注意しなければならないということが分かります。
なお、スナップショットが150個ほどある状態で、再度delete-aes-manual-snapshot
を実行した場合のCloudWatch Logsは以下の通りです。
snapshot_list object is empty.
と表示され、削除対象のスナップショットが存在しないことが分かります。また、実行時間は約2秒でした。
手動スナップショットを使ったリストア
リストア前のインデックスの確認
それでは、作成した手動スナップショットを使ってリストアをしていきます。
その前に、リストア前のインデックスを確認します。
KibanaにてIndex Management
- Indices
より、リストア対象であるCloudTrailのインデックスを確認します。
log-aws-cloudtrail-2021-08
とlog-aws-cloudtrail-000001
の2つのインデックスが存在していることが確認できます。SIEM on Amazon ESのデフォルトでは、インデックスを月単位で作成するため、実データを保存しているインデックスはlog-aws-cloudtrail-2021-08
の方になります。
また、リストア前のデータ件数も確認します。Discoverより、直近3日間のCloudTrailのデータ件数は、46,683件であることが分かります。
インデックスのクローズ
続いて、リストアの前準備として、リストア対象のインデックスをクローズします。
インデックスをクローズすることで、読み取り/書き込み操作をブロックします。結果として、DashboardやDiscoverなどで検索したときに検索対象から外れます。
Amazon ES 7.1よりも古いバージョンでは、インデックスのクローズ(/index-name/_close
)がサポートされていませんでした。
しかし、AWS公式ドキュメントを確認すると、Amazon ES 7.1以降から/index-name/_close
をサポートしています。2021/8/8時点のSIEM on Amazon ESのAmazon ESドメインのバージョンは7.9であるため、インデックスのクローズを行うことができます。
なお、インデックスが存在(オープン)している状態でリストアをしようとすると、以下のように「同じ名前のインデックスがクラスターに存在するためリストアできない」とのメッセージを出力して失敗します。
インデックスをクローズする際は、Dev ToolsからPOST log-aws-cloudtrail-2021-08/_close
と入力して▷
をクリックします。すると、以下のように"acknowledged" : true
と表示され、インデックスのクローズが行えます。
インデックスのクローズ後の状態を確認します。
Discoverより、log-aws-cloudtrail-2021-08
クローズ後のデータを確認すると、「検索条件に一致する結果がありません」とのメッセージが出力されました。確かにCloudTrailのインデックスがクローズされていおり、検索対象範囲外になっていることが分かります。
手動スナップショットを使ったリストア
それでは、手動スナップショットを使ってリストアします。
リストアで使用するスナップショットは、作業時点で最も新しいmanual-snapshot-test-2021-08-09t00-00-43
を使います。
スナップショットの作成開始時間(start_time
)が2021-08-09T00:00:43.496Z
であることから、日本時間の2021/8/9 9時ごろの状態にリストアできるはずです。
スナップショットmanual-snapshot-test-2021-08-09t00-00-43
を使って、インデックスlog-aws-cloudtrail-2021-08
をリストアをする際のコマンドは以下の通りです。
POST _snapshot/manual/manual-snapshot-test-2021-08-09t00-00-43/_restore { "indices": "log-aws-cloudtrail-2021-08" }
コマンドの実行結果は以下の通りです。
レスポンスとして{"accepted" : true}
が返ってきており、正常にリストアできていそうです。また、実行時間は約10秒でした。
Discoverより、log-aws-cloudtrail-2021-08
リストア後のデータを確認すると、44,882件のデータがあることが確認できます。
リストア前のデータ件数は46,683件でしたが、これはリストア前のデータ確認時の時間と、スナップショットの作成時間が異なるためです。
表示するデータの範囲を直近3時間に変更すると、意図した通り、2021/8/9 9時ごろの状態にリストアできていることが確認できます。
手動スナップショットが必要なのであれば作成しておこう
Amazon ESのスナップショットの管理をLambda関数を使って行ってみました。
今まで、Amazon ESの手動スナップショットを作成したことはなかったですが、スナップショットはトラブル時の頼み綱でもあるので、トラブル時にスムーズに対応できるよう、このような事前検証は非常に大事だなと実感しました。
なお、この記事を書き終えた直後(2021/8/10)に、SIEM on Amazon ESのAmazon ESドメインのデフォルトのバージョンが7.10
になりました。
Curatorを使う際は、本記事で紹介した通り、依存関係に気をつけてクライアントのElasticsearchのバージョンを合わせるようにしてください。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!